#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time      :2021/6/24 10:35
# @Author    :cjw
import os
from typing import List, Type, Union, Optional

from selenium.webdriver import Remote
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException

from web_wework.browser.chrome import Chrome
from web_wework.util.log import logger
from web_wework.util.file_operator import YmlOperator
from web_wework.setting import WORK_WEI_XIN_URL, COOKIES_YML, BY_RULES, WEBDRIVER_WAIT_TIMEOUT


class BasePage:
	"""页面基类，把页面重复的步骤抽离出来"""

	locators = {}  # 元素定位器

	def __init__(self, base_driver: Optional[WebDriver] = None, is_remote: bool = False):
		"""
		定义base_driver参数，解决driver 重复实例化的问题
		:param base_driver: 起始浏览器对象
		:param is_remote: 是否启动远程服务
		"""
		if not base_driver:
			# 获取命令行中传递过来的browser参数
			browser = os.getenv('browser')

			# 如果传递过来的browser参数未传递，默认启动chrome
			if browser:
				if browser.lower() == 'firefox':
					pass
				elif browser.lower() == 'edge':
					pass
			else:
				if is_remote:  # 是否启动远程服务
					self.driver: WebDriver = Chrome(browser_type=Remote).browser
				else:
					self.driver: WebDriver = Chrome().browser

			self.driver.get(WORK_WEI_XIN_URL)  # 打开企业微信登录页

			# 读取yml文件中的cookies中的信息
			cookies = YmlOperator(COOKIES_YML).data
			# 设置cookie,cookie只能和对应的匹配域名才能设置
			for cookie in cookies:
				self.driver.add_cookie(cookie)
			self.driver.refresh()  # 刷新网页
		else:
			self.driver = base_driver

	def __getattr__(self, name: str):
		"""
		当使用【实例.属性】获取属性值时，如果实例没有该属性，就会调用这个方法
		用于实例属性访问，获取locators元素定位器中的值。
		locators元素定位的值保存在yml文件中,以字典的形式保存，
		字典的键是定位元素的名称，值是定位表达式【查找方式，查找值，flag标志存在即为查找多个】
		yml文件中保存的格式：
		department_name: # 部门名称
		  - xpath
		  - //ul[@role="group"]//a
		  - 1 # 这个值判断是否查找多个元素
		:param name: 定位元素的名称
		:return:
		"""
		if name not in self.locators:
			logger.error(f'元素定位器locators中无定位属性的键：{name}')
			raise ValueError(f'元素定位器locators中无定位属性的键：{name}')

		by, val, *flag = self.locators[name]
		if by not in BY_RULES:
			logger.error(f'不支持该元素定位方式：{by}，定位内容：{val}')
			raise ValueError(f'不支持该元素定位方式：{by}，定位内容：{val}')

		try:
			if flag:  # flag变量如果存在，表示需要查找多个元素
				element = WebDriverWait(self.driver, WEBDRIVER_WAIT_TIMEOUT).until(
					lambda x: x.find_elements(by, val))
			else:
				element = WebDriverWait(self.driver, WEBDRIVER_WAIT_TIMEOUT).until(
					lambda x: x.find_element(by, val))
			return element
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			logger.error(f'查找元素超时，元素定位方式：{by}, 定位内容：{val}')
			raise TimeoutException(f'查找元素超时，元素定位方式：{by}, 定位内容：{val}', screen, stacktrace)

	def find(self, by: Type[Union[str, tuple, list]], val=None, timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> WebElement:
		"""
		查找单个可见元素:
		两种传入定位元素的方式，提高代码的兼容性
		如果传入的是元祖,那就只有一个参数
		:param by: 定位方式css、id，或者(定位方式，定位内容)
		:param val: 定位内容
		:param timeout: 显式等待时间，默认是10秒
		:return:
		"""
		try:
			if val:  # 判断接收定位元素值的方式
				if by not in BY_RULES:
					logger.error(f'不支持该元素定位方式：{by}，定位内容: {val}')
					raise ValueError(f'不支持该元素定位方式：{by}，定位内容: {val}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_element_located((by, val)))
			else:
				if by[0] not in BY_RULES:
					logger.error(f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
					raise ValueError(f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_element_located(by))
			return element
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			if val:
				logger.error(f'查找单个元素超时，元素定位方式：{by}, 定位内容：{val}')
				raise TimeoutException(f'查找单个元素超时，元素定位方式：{by}, 定位内容：{val}', screen, stacktrace)
			else:
				logger.error(f'查找单个元素超时，元素定位内容：{by}')
				raise TimeoutException(f'查找单个元素超时，元素定位内容：{by}', screen, stacktrace)

	def finds(self, by: Type[Union[str, tuple, list]], val=None, timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> List[WebElement]:
		"""
		查找多个可见元素
		:param by: 定位方式css、id，或者(定位方式，定位值)
		:param val: 定位内容
		:param timeout: 显式等待时间，默认是10秒
		:return:
		"""
		try:
			if val:  # 判断接收定位元素值的方式
				if by not in BY_RULES:
					logger.error(f'不支持该元素定位方式：{by}，定位内容: {val}')
					raise ValueError(f'不支持该元素定位方式：{by}，定位内容: {val}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_all_elements_located((by, val)))
			else:
				if by[0] not in BY_RULES:
					logger.error(f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
					raise ValueError(f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_all_elements_located(by))
			return element
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			if val:
				logger.error(f'查找多个元素超时，元素定位方式：{by}, 定位内容：{val}')
				raise TimeoutException(f'查找多个元素超时，元素定位方式：{by}, 定位内容：{val}', screen, stacktrace)
			else:
				logger.error(f'查找多个元素超时，元素定位内容：{by}')
				raise TimeoutException(f'查找多个元素超时，元素定位内容：{by}', screen, stacktrace)

	def click(self, element: WebElement):
		"""
		按钮点击,点击的元素上面还有别的东西覆盖，将改成js点击
		:param element: 需要点击的元素
		:return:
		"""
		try:
			element.click()
		except ElementClickInterceptedException as exc:
			self.driver.execute_script('arguments[0].click();', element)

	def send_keys(self, element: WebElement, value, mode: str = None):
		"""
		先清空文本输入框原有值，再输入内容
		输入方式：
		:param element: 文本输入框
		:param value: 输入的值
		:param mode: 输入方式：js,模拟键盘(ctrl_a)输入，send_keys输入(默认)
		:return:
		"""
		if mode.lower() == 'ctrl_a':
			# 通过键盘全选，然后直接输入新的内容，就不用clear了
			element.send_keys(Keys.CONTROL, 'a')
			element.send_keys(value)
		elif mode.lower() == 'js':
			self.driver.execute_script(f'arguments[{element}].value = "";')
			self.driver.execute_script(f'arguments[{element}].value = {value};')
		else:
			element.click()
			element.clear()
			element.send_keys(value)
